package com.bitbakery.plugin.cl.lang.parser;

import static com.bitbakery.plugin.cl.CLConstants.*;
import com.bitbakery.plugin.cl.lang.lexer.CLTokenTypes;
import static com.bitbakery.plugin.cl.lang.lexer.CLTokenTypes.*;
import com.bitbakery.plugin.cl.lang.psi.CLElementTypes;
import static com.bitbakery.plugin.cl.lang.psi.CLElementTypes.*;
import com.intellij.lang.PsiBuilder;

public class CLDefunParser {
    protected static void parseFunctionName(PsiBuilder builder) {
        PsiBuilder.Marker marker = builder.mark();
        if (builder.getTokenType() != CLTokenTypes.SYMBOL) {
            builder.error(ERROR_EXPECTED_FUNCTION_NAME);
            marker.drop();
        } else {
            advanceLexer(builder);
            marker.done(FUNCTION_NAME);
        }
    }

    public static void parseDefun(PsiBuilder builder) {
        advanceLexer(builder);

        parseFunctionName(builder);
        parseFormalParameterList(builder);
        parseDocstring(builder);
        parseFunctionBody(builder);

        if (builder.getTokenType() != RIGHT_PAREN) {
            builder.error(ERROR_EXPECTED_RIGHT_PAREN);
        } else {
            advanceLexer(builder);
        }
    }

    public static void parseLambda(PsiBuilder builder) {
        builder.advanceLexer();

        parseFormalParameterList(builder);
        parseDocstring(builder);
        parseFunctionBody(builder);

        builder.advanceLexer();
    }

    protected static void parseFormalParameterList(PsiBuilder builder) {
        PsiBuilder.Marker marker = builder.mark();

        if (builder.getTokenType() != LEFT_PAREN) {
            builder.error(ERROR_EXPECTED_LEFT_PAREN);
        } else {
            advanceLexer(builder);
        }

        parseRegularParameterList(builder);
        parseOptionalParameterList(builder);
        parseKeywordParameterList(builder);
        parseRestParameterList(builder);

        // TODO - Need to parse the &aux keyword and any associated parameters!!

        if (builder.getTokenType() != RIGHT_PAREN) {
            builder.error(ERROR_EXPECTED_RIGHT_PAREN);
        } else {
            advanceLexer(builder);
        }
        marker.done(FORMAL_PARAMETER_LIST);
    }

    /**
     * Input: Builder must be pointed at the first regular parameter (if any).
     * Output: Builder is pointed at either:
     * (a) Closing right paren of the formal paramter list
     * (b) The &optional token
     * (c) The &key token
     * (d) The &rest token
     */
    private static void parseRegularParameterList(PsiBuilder builder) {
        if (builder.getTokenType() == OPTIONAL
                || builder.getTokenType() == KEY
                || builder.getTokenType() == REST
                || builder.getTokenType() == RIGHT_PAREN) {
            return;
        }

        PsiBuilder.Marker listMarker = builder.mark();
        while (builder.getTokenType() != null
                && builder.getTokenType() != RIGHT_PAREN
                && builder.getTokenType() != OPTIONAL
                && builder.getTokenType() != KEY
                && builder.getTokenType() != REST) {

            PsiBuilder.Marker marker = builder.mark();
            advanceLexer(builder); // TODO - This should be a symbol, methinks...
            marker.done(REGULAR_PARAMETER);
        }

        if (builder.getTokenType() == null) {
            builder.error(ERROR_EXPECTED_RIGHT_PAREN);
        }
        listMarker.done(REGULAR_PARAMETER_LIST);
    }

    /**
     * Input: Builder is pointed at some token in the formal parameter list
     * Output: Builder is pointed at either:
     * (a) Closing right paren of the formal paramter list
     * (c) The &key token
     * (d) The &rest token
     */
    private static void parseOptionalParameterList(PsiBuilder builder) {
        if (builder.getTokenType() != OPTIONAL) {
            return;
        }
        advanceLexer(builder);
        PsiBuilder.Marker listMarker = builder.mark();

        while (builder.getTokenType() != null
                && builder.getTokenType() != RIGHT_PAREN
                && builder.getTokenType() != KEY
                && builder.getTokenType() != REST) {

            PsiBuilder.Marker marker = builder.mark();
            advanceLexer(builder); // TODO - This should be a symbol, methinks...
            marker.done(OPTIONAL_PARAMETER);
        }

        if (builder.getTokenType() == null) {
            builder.error(ERROR_EXPECTED_RIGHT_PAREN);
        }

        listMarker.done(OPTIONAL_PARAMETER_LIST);
    }

    /**
     * Input: Lexer must be pointed at the first token following any optional parameters (if any).
     * Output: Lexer is pointed at either:
     * (a) Closing right paren of the formal paramter list
     * (d) The &rest token
     */
    private static void parseKeywordParameterList(PsiBuilder builder) {
        if (builder.getTokenType() != KEY) {
            return;
        }
        advanceLexer(builder);
        PsiBuilder.Marker listMarker = builder.mark();

        while (builder.getTokenType() != null
                && builder.getTokenType() != RIGHT_PAREN
                && builder.getTokenType() != REST) {

            PsiBuilder.Marker marker = builder.mark();
            // TODO - This is a little trickier: each keyword parameter has a keyword and a something else, and we need both!
            advanceLexer(builder); // TODO - This should be a symbol, methinks...
            marker.done(KEYWORD_PARAMETER);
        }

        if (builder.getTokenType() == null) {
            builder.error(ERROR_EXPECTED_RIGHT_PAREN);
        }

        listMarker.done(KEYWORD_PARAMETER_LIST);
    }

    /**
     * Input: Lexer must be pointed at the first token following any keyword parameters
     * Output: Lexer is pointed at the closing right paren of the formal paramter list
     */
    private static void parseRestParameterList(PsiBuilder builder) {
        if (builder.getTokenType() != KEY) {
            return;
        }

        PsiBuilder.Marker marker = builder.mark();
        // TODO - Check that we have a symbol, methinks??
        advanceLexer(builder);
        marker.done(REST_PARAMETER);

        // TODO - Validate that we have a single rest parameter!
    }

    protected static void parseDocstring(PsiBuilder builder) {
        if (builder.getTokenType() == CLTokenTypes.STRING_LITERAL) {
            PsiBuilder.Marker marker = builder.mark();
            advanceLexer(builder);
            if (builder.getTokenType() == CLTokenTypes.RIGHT_PAREN) {
                marker.rollbackTo();
                return;
            }
            marker.done(CLElementTypes.DOCSTRING);
        }
    }

    public static void parseFunctionBody(PsiBuilder builder) {
        PsiBuilder.Marker marker = builder.mark();
        while (builder.getTokenType() != null && builder.getTokenType() != CLTokenTypes.RIGHT_PAREN) {
            CLParser.parseStatement(builder);
        }
        marker.done(CLElementTypes.FUNCTION_BODY);
    }

    private static void advanceLexer(PsiBuilder builder) {
        if (builder.getTokenType() != null) {
            builder.advanceLexer();
        }
    }
}
